/*
 * ULSF Sagittarius IDA
 * Copyright (C) 2012, John Pritchard, all rights reserved.
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
package sagittarius;

import json.Json;
import json.Reader;

import sagittarius.design.Axes;
import sagittarius.design.Body;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;

import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Align;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Slider;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;

/**
 * 
 * @author jdp
 */
public class Sagittarius
    extends Controller
    implements com.badlogic.gdx.ApplicationListener
{
    private final static Color ColorBody = new Color(0.8f,0.8f,0.9f,1.0f);

    private final static Vector3 LightNormal = new Vector3(1,1,3).nor();

    private final static Matrix4 UCam = new Matrix4();

    private final static float Material = 2.4f;

    private final static String DefaultDescription = "ulsf-sagittarius/sagittarius-beta.json";


    private Stage stage;

    private Label length, radius, span;
    private Label alpha, beta, gamma, delta, epsilon;
    private Label zeta, eta, theta, iota, kappa;
    private TextField file;
    private boolean fileExternal = false, fileInternal = false;

    private final InputMultiplexer multiplexer = new InputMultiplexer();

    private final Table table = new Table().pad(1);

    private Visualization body = new Body(), axisX = new Axes.X(), axisY = new Axes.Y(), axisZ = new Axes.Z();

    private Visualization airfoil;

    private ShaderProgram bodyShader, axesShader;

    private boolean alive = false;



    public Sagittarius(Viewport viewport){
        super(viewport);
    }
    public Sagittarius(){
        super();
    }



    @Override
    public void create(){

        final Skin skin = new Skin(Gdx.files.internal("data/uiskin.json"), Gdx.files.internal("data/uiskin.png"));

        this.stage = new Stage(this.width,this.height,true);

        final Slider length = new Slider(Field.Length.min, Field.Length.max, Field.Length.inc, skin);
        length.setValue(this.body.getLength());
        this.length = new Label(Field.Length.format(this.body), skin);

        final Slider radius = new Slider(Field.Radius.min, Field.Radius.max, Field.Radius.inc, skin);
        radius.setValue(this.body.getRadius());
        this.radius = new Label(Field.Radius.format(this.body), skin);

        final Slider span = new Slider(Field.Span.min, Field.Span.max, Field.Span.inc, skin);
        span.setValue(this.body.getSpan());
        this.span = new Label(Field.Span.format(this.body), skin);

        final Slider alpha = new Slider(Field.Alpha.min, Field.Alpha.max, Field.Alpha.inc, skin);
        alpha.setValue(this.body.getAlpha());
        this.alpha = new Label(Field.Alpha.format(this.body), skin);

        final Slider beta = new Slider(Field.Beta.min, Field.Beta.max, Field.Beta.inc, skin);
        beta.setValue(this.body.getBeta());
        this.beta = new Label(Field.Beta.format(this.body), skin);

        final Slider gamma = new Slider(Field.Gamma.min, Field.Gamma.max, Field.Gamma.inc, skin);
        gamma.setValue(this.body.getGamma());
        this.gamma = new Label(Field.Gamma.format(this.body), skin);

        final Slider delta = new Slider(Field.Delta.min, Field.Delta.max, Field.Delta.inc, skin);
        delta.setValue(this.body.getDelta());
        this.delta = new Label(Field.Delta.format(this.body), skin);

        final Slider epsilon = new Slider(Field.Epsilon.min, Field.Epsilon.max, Field.Epsilon.inc, skin);
        epsilon.setValue(this.body.getEpsilon());
        this.epsilon = new Label(Field.Epsilon.format(this.body), skin);

        final Slider zeta = new Slider(Field.Zeta.min, Field.Zeta.max, Field.Zeta.inc, skin);
        zeta.setValue(this.body.getZeta());
        this.zeta = new Label(Field.Zeta.format(this.body), skin);

        final Slider eta = new Slider(Field.Eta.min, Field.Eta.max, Field.Eta.inc, skin);
        eta.setValue(this.body.getEta());
        this.eta = new Label(Field.Eta.format(this.body), skin);

        final TextButton defaults = new TextButton("Defaults",skin);
        final TextButton neutrals = new TextButton("Neutrals",skin);
        final TextButton load, store;

        this.fileInternal = Gdx.files.isLocalStorageAvailable();
        this.fileExternal = Gdx.files.isExternalStorageAvailable();

        if (this.fileExternal || this.fileInternal){
            load = new TextButton("Load",skin);
            store = new TextButton("Store",skin);
            this.file = new TextField(DefaultDescription,skin){

                    public float getPrefWidth(){
                        return 300.0f;
                    }
                };
        }
        else {
            load = null;
            store = null;
            this.file = null;
        }
        /*
         */
        this.table.width = this.width;
        this.table.height = this.height;
        this.table.align(Align.BOTTOM | Align.LEFT);
        this.table.add(length).pad(1);
        this.table.add(radius).pad(1);
        this.table.add(span).pad(1);
        this.table.row();
        this.table.add(this.length).pad(1);
        this.table.add(this.radius).pad(1);
        this.table.add(this.span).pad(1);
        this.table.row();
        this.table.add(alpha).pad(1);
        this.table.add(beta).pad(1);
        this.table.add(gamma).pad(1);
        this.table.row();
        this.table.add(this.alpha).pad(1);
        this.table.add(this.beta).pad(1);
        this.table.add(this.gamma).pad(1);
        this.table.row();
        this.table.add(delta).pad(1);
        this.table.add(epsilon).pad(1);
        this.table.add(zeta).pad(1);
        this.table.add(eta).pad(1);
        this.table.row();
        this.table.add(this.delta).pad(1);
        this.table.add(this.epsilon).pad(1);
        this.table.add(this.zeta).pad(1);
        this.table.add(this.eta).pad(1);
        this.table.row();
        Table subtable = new Table().pad(1);

        subtable.add(defaults).pad(1);
        subtable.add(neutrals).pad(1);
        if (null != load){
            subtable.add(load).pad(1);
            subtable.add(store).pad(1);
            subtable.add(this.file).pad(1).colspan(3);
        }
        this.table.add(subtable).pad(1).colspan(4);

        this.stage.addActor(table);

        length.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Length,value);
                    Sagittarius.this.length.setText(Field.Length.format(Sagittarius.this.body));
                }
            });
        radius.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Radius,value);
                    Sagittarius.this.radius.setText(Field.Radius.format(Sagittarius.this.body));
                }
            });
        span.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Span,value);
                    Sagittarius.this.span.setText(Field.Span.format(Sagittarius.this.body));
                }
            });
        alpha.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Alpha,value);
                    Sagittarius.this.alpha.setText(Field.Alpha.format(Sagittarius.this.body));
                }
            });
        beta.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Beta,value);
                    Sagittarius.this.beta.setText(Field.Beta.format(Sagittarius.this.body));
                }
            });
        gamma.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Gamma,value);
                    Sagittarius.this.gamma.setText(Field.Gamma.format(Sagittarius.this.body));
                }
            });
        delta.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Delta,value);
                    Sagittarius.this.delta.setText(Field.Delta.format(Sagittarius.this.body));
                }
            });
        epsilon.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Epsilon,value);
                    Sagittarius.this.epsilon.setText(Field.Epsilon.format(Sagittarius.this.body));
                }
            });
        zeta.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Zeta,value);
                    Sagittarius.this.zeta.setText(Field.Zeta.format(Sagittarius.this.body));
                }
            });
        eta.setValueChangedListener(new Slider.ValueChangedListener() {
                @Override
                public void changed (Slider slider, float value) {

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Field.Eta,value);
                    Sagittarius.this.eta.setText(Field.Eta.format(Sagittarius.this.body));
                }
            });
        defaults.setClickListener(new ClickListener(){
                @Override
                public void click (Actor actor, float x, float y){

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Values.Defaults);
                }
            });
        neutrals.setClickListener(new ClickListener(){
                @Override
                public void click (Actor actor, float x, float y){

                    Sagittarius.this.body = new Body(Sagittarius.this.body,Values.Neutrals);
                }
            });
        if (null != load){
            load.setClickListener(new ClickListener(){
                    @Override
                    public void click (Actor actor, float x, float y){
                        try {
                            final FileHandle file;

                            if (Sagittarius.this.fileExternal){

                                file = Gdx.files.external(Sagittarius.this.file.getText());
                            }
                            else {

                                file = Gdx.files.internal(Sagittarius.this.file.getText());
                            }

                            final String string = file.readString("UTF-8");

                            if (null != string){

                                final Reader reader = new Reader();

                                final Json json = reader.read(string);

                                Sagittarius.this.body = new Body(Sagittarius.this.body,json);
                            }
                        }
                        catch (Exception exc){
                            exc.printStackTrace();
                        }
                    }
                });
            store.setClickListener(new ClickListener(){
                    @Override
                    public void click (Actor actor, float x, float y){
                        try {
                            final String string = Sagittarius.this.body.toJson().toString();

                            final FileHandle file;

                            if (Sagittarius.this.fileExternal){

                                file = Gdx.files.external(Sagittarius.this.file.getText());
                            }
                            else {

                                file = Gdx.files.internal(Sagittarius.this.file.getText());
                            }
                            file.writeString(string+'\n',false);
                        }
                        catch (Exception exc){
                            exc.printStackTrace();
                        }
                    }
                });
        }

        this.multiplexer.addProcessor(this.stage);
        this.multiplexer.addProcessor(this);
        Gdx.input.setInputProcessor(this.multiplexer);

        ShaderProgram.pedantic = false;
        this.bodyShader = new ShaderProgram(Gdx.files.internal("data/shaders/body.vert.glsl").readString(),
                                        Gdx.files.internal("data/shaders/body.frag.glsl").readString());

        this.alive = this.bodyShader.isCompiled();
        if (!this.alive) {
            Gdx.app.error("Sagittarius","Error compiling body shader " + this.bodyShader.getLog());
            Gdx.app.exit();
        }

        this.axesShader = new ShaderProgram(Gdx.files.internal("data/shaders/axes.vert.glsl").readString(),
                                        Gdx.files.internal("data/shaders/axes.frag.glsl").readString());

        this.alive = this.axesShader.isCompiled();
        if (!this.alive) {
            Gdx.app.error("Sagittarius","Error compiling axes shader " + this.axesShader.getLog());
            Gdx.app.exit();
        }
    }
    @Override
    public void resize (int width, int height){

        this.table.width = this.width;
        this.table.height = this.height;
        this.table.invalidate();

        this.stage.setViewport(this.width,this.height,true);

        super.resize(width,height);
    }
    @Override
    public void render(){
        if (this.alive){
            GLCommon gl = Gdx.gl;
            gl.glViewport(0, 0, this.width, this.height);
            gl.glClearColor(0.4f, 0.4f, 0.4f, 1);
            gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            gl.glEnable(GL10.GL_DEPTH_TEST);
            gl.glEnable(GL10.GL_CULL_FACE);

            /*
             */
            final ShaderProgram bodyShader = this.bodyShader;
            Mesh mesh = this.body.getMesh();
            final Matrix4 camera = this.getCamera();
            if (null != mesh){
                bodyShader.begin();

                bodyShader.setUniformMatrix("u_camera", camera);
                bodyShader.setUniformf("u_color", ColorBody);
                bodyShader.setUniformf("u_light", LightNormal);
                bodyShader.setUniformf("u_mat", Material);

                mesh.render(bodyShader, GL20.GL_TRIANGLES);

                bodyShader.end();
            }
            mesh = this.axisX.getMesh();
            {
                axesShader.begin();

                axesShader.setUniformMatrix("u_camera", camera);
                axesShader.setUniformf("u_color", Color.RED);

                mesh.render(axesShader, GL20.GL_LINES);

                axesShader.end();
            }
            mesh = this.axisY.getMesh();
            {
                axesShader.begin();

                axesShader.setUniformMatrix("u_camera", camera);
                axesShader.setUniformf("u_color", Color.GREEN);

                mesh.render(axesShader, GL20.GL_LINES);

                axesShader.end();
            }
            mesh = this.axisZ.getMesh();
            {
                axesShader.begin();

                axesShader.setUniformMatrix("u_camera", camera);
                axesShader.setUniformf("u_color", Color.BLUE);

                mesh.render(axesShader, GL20.GL_LINES);

                axesShader.end();
            }
            mesh = ((Body)this.body).getAirfoilMesh();
            if (null != mesh){
                axesShader.begin();

                axesShader.setUniformMatrix("u_camera", UCam);
                axesShader.setUniformf("u_color", Color.LIGHT_GRAY);

                mesh.render(axesShader, GL20.GL_LINES);

                axesShader.end();
            }
            /*
             */
            gl.glDisable(GL10.GL_CULL_FACE);
            gl.glDisable(GL10.GL_DEPTH_TEST);

            if (null != this.stage){
                this.stage.act(Gdx.graphics.getDeltaTime());
                this.stage.draw();
            }
        }
    }
    @Override
    public void pause(){
        this.alive = false;
    }
    @Override
    public void resume(){
        this.alive = true;
    }
    @Override
    public void dispose(){

        if (null != this.stage)
            this.stage.dispose();

        this.body.dispose();

        if (null != this.bodyShader)
            this.bodyShader.dispose();
    }
}
